
// License Notification
//======================================================
// Copyright (c) 2010 Channel Intelligence Inc.
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// which accompanies this distribution, and is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// ======================================================




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace CIP4.Common
{
    [Serializable]
    public class ExternalIDType
    {
        readonly List<object> _parts = new List<object>();

        public ExternalIDType(string part)
        {
            _parts.Add(part);
        }

        private ExternalIDType() { }

        public ExternalIDType(IEnumerable<object> parts)
        {
            _parts.AddRange(parts);
        }

        public ExternalIDType(params object[] parts)
            : this((IEnumerable<object>)parts) { }

        public object GetPart(int index)
        {
            if (index < _parts.Count)
                return _parts[index];

            return null;
        }

        public override string ToString()
        {
            string text = string.Empty;
            foreach (object part in _parts)
            {
                if (part == null)
                    throw new ArgumentException("ExternalID part is null");
                Type partType = part.GetType();
                if (!(partType.IsValueType || partType.Name == "String"))
                    throw new ArgumentException(string.Format("ExternalID parts must be value types: {0} of type {1}", part, partType.Name));
                text += text.Seperator("_") +
                    (partType.Name == "String" ? ExternalIDEscape(part.ToString()) :
                    partType.Name == "DateTime" ? DateTimeString((DateTime)part) :
                    part.ToString());
            }
            return text;
        }

        public static ExternalIDType ExternalIDFromParts(IEnumerable<object> parts)
        {
            if (parts == null || parts.Count() == 0 || parts.All(p => p == null || p.ToString() == string.Empty))
                return default(ExternalIDType);
            ExternalIDType id = new ExternalIDType();
            id._parts.AddRange(parts);
            return id;
        }

        public static ExternalIDType ExternalIDFromEscapedString(string value)
        {
            if (string.IsNullOrEmpty(value))
                return default(ExternalIDType);
            string[] escapedParts = value.Split('_');
            ExternalIDType id = new ExternalIDType();
            foreach(string part in escapedParts)
                id._parts.Add(ExternalIDUnescape(part));
            return id;
        }

        public static string DateTimeString(DateTime x)
        {
            // A slight variation on ISO 8601 format: use dashes in the time instead of colons, which would have required escaping.
            // http://www.w3.org/TR/NOTE-datetime
            DateTime u = x.ToUniversalTime();
            return string.Format("{0:0000}-{1:00}-{2:00}T{3:00}-{4:00}-{5:00}.{6:000}Z",
                u.Year,
                u.Month,
                u.Day,
                u.Hour,
                u.Minute,
                u.Second,
                u.Millisecond
            );
        }

        static readonly Regex DateTimeRegex = new Regex(@"^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2})-([0-9]{2})-([0-9]{2})\.([0-9]{3})Z$");

        public static DateTime ParseDateTime(string s)
        {
            Match m = DateTimeRegex.Match(s);
            if (m.Success)
            {
                int year = int.Parse(m.Groups[1].Value);
                int month = int.Parse(m.Groups[2].Value);
                int day = int.Parse(m.Groups[3].Value);
                int hour = int.Parse(m.Groups[4].Value);
                int minute = int.Parse(m.Groups[5].Value);
                int second = int.Parse(m.Groups[6].Value);
                int milli = int.Parse(m.Groups[7].Value);
                DateTime u = new DateTime(year, month, day, hour, minute, second, milli, DateTimeKind.Utc);
                DateTime d = u.ToLocalTime();
                return d;
            }
            
            throw new ArgumentException(string.Format("Not a valid date/time string: {0}", s));
        }

        /// <summary>
        /// This string is used as a delimiter to separate the parts of a multi-part external identifier.
        /// If changed in the future, this string must remain exactly 1 character long and be a printable 7-bit ASCII value.
        /// </summary>
        public const char ExternalIdentifierDelimiter = '_';

        /// <summary>
        /// Characters that UrlEncode leaves alone, but that we want to escape in the same manner, i.e. "%xx" where x is a hexadecimal digit.
        /// UrlDecode, without modification, will decode these just fine.
        /// </summary>
        private static readonly char[] ExtraEscapeCharacters = { ExternalIdentifierDelimiter, '*' };

        /// <summary>
        /// Converts an arbitrary string value into something that can be safely used by all item wells as an encoded identifier.
        /// We use SerializerBaseType.ExternalIdentifierDelimiter as a delimiter, so occurrences of that string must be escaped.
        /// Likewise, any character which would cause problems in a filename must be escaped, 
        /// along with anything outside the 7-bit ASCII character set, including control characters, Chinese, Arabic, etc.
        /// Use ExternalIDUnescape as the inverse function for this function.
        /// </summary>
        /// <param name="raw"></param>
        /// <returns>An escaped value that is safe to use in any item well.</returns>
        public static string ExternalIDEscape(string raw)
        {
            if (string.IsNullOrEmpty(raw))
                return raw;

#if ESCAPEEXTERNALIDS
            string unencoded = System.Web.HttpUtility.HtmlDecode(raw.ToLower());

            string encoded = System.Web.HttpUtility.UrlEncode(unencoded);
#else
            string encoded = raw;
#endif
            foreach (char c in ExtraEscapeCharacters)
            {
                string e = "%" + ((int)c).ToString("x");
                encoded = encoded.Replace(c.ToString(), e);
            }

            return encoded;


        }

        /// <summary>
        /// The inverse function of ExternalIDEscape: given an escaped string, returns the raw string that escapes to that value. 
        /// </summary>
        /// <param name="encoded">The escaped string to be unescaped.</param>
        /// <returns>The unescaped string value.</returns>
        public static string ExternalIDUnescape(string encoded)
        {
            if (string.IsNullOrEmpty(encoded))
                return encoded;
#if ESCAPEEXTERNALIDS
            return System.Web.HttpUtility.UrlDecode(encoded);
#else
            return encoded;
#endif
        }
    }
}
